﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace nesemu
{
    using System.IO;
    using System.Threading;

    public class GameField : UserControl
    {
        private System.Windows.Controls.Grid LayoutRoot;
        private System.Windows.Controls.MediaElement mediaElement;
        private System.Windows.Controls.Image renderImage;

        internal AppletUI gui;
        internal NES nes;
        internal bool sound = true;
        private bool started;

        public GameField()
        {
            Loaded += OnLoaded;
            SetupThread();
            LayoutRoot = new Grid();
            LayoutRoot.Background = new SolidColorBrush(Colors.Black);
            
            mediaElement = new MediaElement();
            mediaElement.AutoPlay = true;
            renderImage = new Image();
            renderImage.CacheMode = new BitmapCache();
            renderImage.Stretch = Stretch.Uniform;
            renderImage.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
            renderImage.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
            LayoutRoot.Children.Add(mediaElement);
            LayoutRoot.Children.Add(renderImage);
            this.Content = LayoutRoot;
        }

        internal static DependencyProperty SoundEnabledProperty = DependencyProperty.Register("SoundEnabled", typeof(bool), typeof(GameField), new PropertyMetadata(true, SoundEnabledChanged));

        private static void SoundEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            GameField fls = (GameField)d;
            fls.setSound((bool)e.NewValue);
        }

        private void setSound(bool value)
        {
            if (sound != value)
            {
                sound = value;
                if (nes != null)
                {
                    nes.enableSound(sound);
                }
            }
        }

        internal bool SoundEnabled
        {
            get
            {
                return (bool)GetValue(SoundEnabledProperty);
            }
            set
            {
                SetValue(SoundEnabledProperty, value);
            }
        }



        public static DependencyProperty FrameRateProperty = DependencyProperty.Register("FrameRate", typeof(int), typeof(GameField), new PropertyMetadata(FrameRateChanged));
        private int framerate = 50;
        private Stream audioStream;

        private static void FrameRateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((GameField)d).SetFrameRate((int)e.NewValue);
        }

        private void SetFrameRate(int frameRate)
        {
            this.framerate = frameRate;
            SetNesFrameRate();
        }

        private void SetNesFrameRate()
        {
            if (nes != null)
            {
                Application.Current.Host.Settings.MaxFrameRate = framerate;
                nes.Framerate = framerate;
            }
        }

        public int FrameRate
        {
            get
            {
                return (int)GetValue(FrameRateProperty);
            }
            set
            {
                SetValue(FrameRateProperty, value);
            }
        }



        internal bool Started
        {
            get { return started; }
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            base.Focus();
        }

        public void Configure(GameFieldConfig config)
        {
            Config = config;
            //Globals.enableSound = config.SoundEnabled;
            MapKeys();
        }

        internal GameFieldConfig Config { get; set; }

        public void LoadRom(Stream stream)
        {
            stopThread = true;
            if (Config == null)
            {
                Config = new GameFieldConfig();
            }
            this.gui = new AppletUI(this);
            this.gui.init(false);
            MapKeys();
            Globals.memoryFlushValue = 0;
            this.nes = this.gui.NES;
            setSound(sound);
            this.nes.reset();
            this.renderImage.Source = this.nes.GetImage();
            SetNesFrameRate();
            
            if (!this.nes.loadRom(stream))
            {
                throw new Exception("rom not loaded");
            }
            LoadState();
            //nes.stateSave();
            //Start();
        }

        internal void LoadState()
        {
            LoadState(false);
        }

        public void LoadState(bool loadNow)
        {
            if (Config.LoadData != null)
            {
                nes.saveState = new ByteBuffer(Config.LoadData, 0);
                if (loadNow)
                {
                    nes.stateLoad();
                }
            }
        }

        Thread gameThread;

        public void Start()
        {
            if (this.nes.RomValid)
            {
                Config.Events.InvokeGameStarted();
                this.nes.Run();
            }
            else
            {
                throw new Exception("bad rom");
            }
            this.mediaElement.SetSource(this.nes.GetAudio());
            this.started = true;
            CompositionTarget.Rendering += (new EventHandler(this.CompositionTarget_Rendering));
            InitKeyHandlers();
            nes.startEmulation();//Do 1 pass
            stopThread = false;
            gameThread.Start();
            base.Focus();
        }

        private void SetupThread()
        {
            gameThread = new Thread(Emulate) { IsBackground = false };
        }


        private void MapKeys()
        {
            if (gui != null)
            {
                gui.kbJoy1.mapKey(InputHandler.KEY_A, Config.Player1.A);
                gui.kbJoy1.mapKey(InputHandler.KEY_B, Config.Player1.B);
                gui.kbJoy1.mapKey(InputHandler.KEY_START, Config.Player1.Start);
                gui.kbJoy1.mapKey(InputHandler.KEY_SELECT, Config.Player1.Select);
                gui.kbJoy1.mapKey(InputHandler.KEY_UP, Config.Player1.Up);
                gui.kbJoy1.mapKey(InputHandler.KEY_DOWN, Config.Player1.Down);
                gui.kbJoy1.mapKey(InputHandler.KEY_LEFT, Config.Player1.Left);
                gui.kbJoy1.mapKey(InputHandler.KEY_RIGHT, Config.Player1.Right);

                gui.kbJoy2.mapKey(InputHandler.KEY_A, Config.Player2.A);
                gui.kbJoy2.mapKey(InputHandler.KEY_B, Config.Player2.B);
                gui.kbJoy2.mapKey(InputHandler.KEY_START, Config.Player2.Start);
                gui.kbJoy2.mapKey(InputHandler.KEY_SELECT, Config.Player2.Select);
                gui.kbJoy2.mapKey(InputHandler.KEY_UP, Config.Player2.Up);
                gui.kbJoy2.mapKey(InputHandler.KEY_DOWN, Config.Player2.Down);
                gui.kbJoy2.mapKey(InputHandler.KEY_LEFT, Config.Player2.Left);
                gui.kbJoy2.mapKey(InputHandler.KEY_RIGHT, Config.Player2.Right);
            }
        }

        private void InitKeyHandlers()
        {


            DependencyObject parent = Parent;
            do
            {
                FrameworkElement root = parent as FrameworkElement;
                if (root != null)
                {
                    if (root.Parent == null)
                    {
                        root.KeyDown += gui.Joy1.keyPressed;
                        root.KeyDown += keyDown;
                        root.KeyUp += gui.Joy1.keyReleased;
                        root.KeyDown += gui.Joy2.keyPressed;
                        root.KeyUp += gui.Joy2.keyReleased;
                        break;
                    }

                    parent = root.Parent;
                }
            }
            while (true);
        }

        private void keyDown(object sender, KeyEventArgs e)
        {
            if (!e.Handled)
            {
                if (Config != null) 
                {
                    try
                    {
                        if (e.Key == Config.QuickSave)
                        {
                            nes.stateSave();
                            Config.Events.InvokeStateSaved(nes.saveState.getBytes());
                        }
                        if (e.Key == Config.QuickLoad)
                        {
                            nes.stateLoad();
                            Config.Events.InvokeStateLoaded();
                        }
                    }
                    catch (Exception)
                    {
                        
                    }
                }
            }
        }

        public void ToggleSound()
        {
            if (nes != null)
            {
                if (Globals.enableSound)
                {
                    this.mediaElement.Stop();
                    nes.enableSound(false);
                    Config.SoundEnabled = false;
                }
                else
                {
                    nes.enableSound(true);
                    this.mediaElement.SetSource(nes.GetAudio());
                    this.mediaElement.Play();
                    Config.SoundEnabled = true;
                }
            }
        }

        public void TogglePause()
        {
            if (nes != null)
            {
                if (nes.isRunning())
                {
                    Config.Events.InvokeGamePaused();
                    nes.stopEmulation();
                }
                else
                {
                    Config.Events.InvokeGameStarted();
                    nes.startEmulation();
                }
            }
        }

        private bool stopThread = false;


        private void Emulate(object state)
        {
            while (!stopThread)
            {
                nes.readyEvt.WaitOne(20);
                if ((this.nes != null) && (this.nes.IsCPU) && nes.isRunning())
                {
                    nes.Emu();
                }
            }
        }


        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {

            if ((this.nes != null) && (this.nes.IsCPU) && nes.isRunning())
            {
                nes.readyEvt.Set();
                //this.nes.cpu.emulate();//This now done in other thread
                this.nes.InvalidateImage();
                if (Globals.enableSound && nes.GetAudio() != null)
                {
                    nes.WriteSound();
                }
            }
        }

        internal void SetAudioSave(Stream stream)
        {
            audioStream = stream;
        }
    }
}